package com.matrix.framework.auth.controller;

import com.matrix.framework.auth.data.UserPo;
import com.matrix.framework.auth.data.UserVo;
import com.matrix.framework.auth.service.AuthService;
import com.matrix.framework.auth.service.LoginFailRecordService;
import com.matrix.framework.core.annotation.validation.LogCollector;
import com.matrix.framework.core.common.enums.auth.UserLock;
import com.matrix.framework.core.common.result.Result;
import com.matrix.framework.core.component.Jwt;
import com.matrix.framework.core.config.MatrixSecurityConfiguration;
import com.matrix.framework.core.i18n.I18n;
import com.matrix.framework.core.i18n.MessageConstants;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.util.function.Function;

/**
 * 用户登录认证控制器
 *
 * Copyright © 海平面工作室 版权所有
 *
 * @Author: Leo
 * @Create: 2024/10/3 11:15
 * @Since 1.0
 */
//@Api(tags = "用户认证接口")
@RestController
@RequestMapping("/auth")
public class AuthController {

    private final AuthService authService;

    private final PasswordEncoder passwordEncoder;

    private final Jwt jwt;

    private final MatrixSecurityConfiguration securityConfiguration;
    private final LoginFailRecordService loginFailRecordService;

    public AuthController(AuthService authService, PasswordEncoder passwordEncoder, Jwt jwt, MatrixSecurityConfiguration securityConfiguration, LoginFailRecordService loginFailRecordService) {
        this.authService = authService;
        this.passwordEncoder = passwordEncoder;
        this.jwt = jwt;
        this.securityConfiguration = securityConfiguration;
        this.loginFailRecordService = loginFailRecordService;
    }

    @LogCollector
    @PostMapping("/login")
    public Mono<ResponseEntity<Result<UserVo>>> login(@RequestBody UserVo loginRequest, ServerWebExchange request) {
        return authService.findByUsername(loginRequest.getUsername())
                .publishOn(Schedulers.boundedElastic())
                .flatMap(user -> checkUserLockStatus(user))
                .flatMap(user -> handleLogin(user, loginRequest.getPassword()))
                .onErrorResume(e -> {
                    if ("USER_LOCKED".equals(e.getMessage())) {
                        return createLockedUserResponse("PW_LOCK");
                    }
                    return createLoginFailResponse();
                })
                .switchIfEmpty(Mono.defer(this::createLoginFailResponse));
    }

    /**
     * 处理登录逻辑
     */
    private Mono<ResponseEntity<Result<UserVo>>> handleLogin(UserPo user, String password) {
        // 验证密码
        if (!passwordEncoder.matches(password, user.getPassword())) {
            // 记录登录失败
            return loginFailRecordService.recordLoginFail(user.getUsername())
                    .then(createLoginFailResponse());
        }

        // 密码验证成功，清除失败记录
        return loginFailRecordService.clearLoginFailRecord(user.getUsername())
                .then(handleSuccessfulLogin(user));
    }

    /**
     * 处理登录成功的情况
     */
    private Mono<ResponseEntity<Result<UserVo>>> handleSuccessfulLogin(UserPo user) {
        // 先判断是否是首次登录
        boolean isFirstLogin = isFirstLogin(user);
        
        // 如果不是首次登录，检查登录时间间隔
        if (!isFirstLogin) {
            return checkLoginTimeInterval(user);
        } else {
            // 首次登录不更新lastLoginTime，直接返回需要改密码的响应
            return createSuccessfulLoginResponse(user, true);
        }
    }

    /**
     * 检查登录时间间隔并处理相应的锁定状态
     */
    private Mono<ResponseEntity<Result<UserVo>>> checkLoginTimeInterval(UserPo user) {
        // 检查是否为管理员（attr="r"），管理员不进行时间锁定检查
        if ("r".equals(user.getAttr())) {
            // 管理员直接更新最后登录时间并返回成功响应
            user.setLastLoginTime(System.currentTimeMillis());
            return authService.updateLastLoginTime(user)
                    .then(Mono.defer(() -> createSuccessfulLoginResponse(user, false)));
        }
        
        long currentTime = System.currentTimeMillis();
        long lastLoginTime = user.getLastLoginTime();
        long timeDiff = currentTime - lastLoginTime;
        
        // 时间间隔常量（毫秒）
        long THREE_MONTHS = 3L * 30L * 24L * 60L * 60L * 1000L; // 3个月
        long TWELVE_MONTHS = 12L * 30L * 24L * 60L * 60L * 1000L; // 12个月
        
        // 检查是否需要锁定
        if (timeDiff >= TWELVE_MONTHS) {
            // 12个月以上，设置为DISABLED
            user.setStatus(UserLock.DISABLED.name());
            user.setUpdateTime(currentTime);
            return authService.updateUserStatus(user)
                    .then(Mono.defer(() -> createLockedUserResponse(UserLock.DISABLED.name())));
        } else if (timeDiff >= THREE_MONTHS) {
            // 3个月以上，设置为TIMEOUT_LOCK
            user.setStatus(UserLock.TIMEOUT_LOCK.name());
            user.setUpdateTime(currentTime);
            return authService.updateUserStatus(user)
                    .then(Mono.defer(() -> createLockedUserResponse(UserLock.TIMEOUT_LOCK.name())));
        } else {
            // 正常情况，更新最后登录时间
            user.setLastLoginTime(currentTime);
            return authService.updateLastLoginTime(user)
                    .then(Mono.defer(() -> createSuccessfulLoginResponse(user, false)));
        }
    }

    /**
     * 创建成功登录响应
     */
    private Mono<ResponseEntity<Result<UserVo>>> createSuccessfulLoginResponse(UserPo user, boolean needChangePassword) {
        String token = jwt.generateToken("" + user.getId(), user.getUsername());
        UserVo userVo = new UserVo();
        userVo.setAccessToken(token);

        return authService.getUserRoles(user.getUsername())
                .map(roleResult -> {
                    userVo.setId(user.getId());
                    userVo.setUsername(user.getUsername());
                    userVo.setRealName(user.getRealName());
                    userVo.setPassword("******");
                    userVo.setRoles(roleResult.getRoles());
                    
                    // 检查是否需要改密码
                    if (needChangePassword && securityConfiguration.isFirstLoginChangePassword()) {
                        userVo.setFirstLogin(true);
                        userVo.setMessage(I18n.getMessage("first.login.change.password.required"));
                    }
                    
                    return ResponseEntity.ok(Result.ok(userVo));
                });
    }

    /**
     * 检查用户锁定状态
     */
    private Mono<UserPo> checkUserLockStatus(UserPo user) {
        // 检查用户是否已被锁定
        if (!UserLock.NORMAL.name().equals(user.getStatus())) {
            return Mono.error(new RuntimeException("USER_LOCKED"));
        }
        return Mono.just(user);
    }

    /**
     * 判断是否是首次登录
     */
    private boolean isFirstLogin(UserPo user) {
        return user.getLastLoginTime() == null || user.getLastLoginTime() == 0L;
    }

    /**
     * 创建锁定用户响应
     */
    private Mono<ResponseEntity<Result<UserVo>>> createLockedUserResponse(String status) {
        String message;
        if (UserLock.PW_LOCK.name().equals(status)) {
            message = I18n.getMessage("auth.user.locked.password") + 
                    "，" + I18n.getMessage("auth.user.contact.admin");
        } else if (UserLock.TIMEOUT_LOCK.name().equals(status)) {
            message = I18n.getMessage("auth.user.locked.timeout") + 
                    "，" + I18n.getMessage("auth.user.contact.admin");
        } else if (UserLock.DISABLED.name().equals(status)) {
            message = I18n.getMessage("auth.user.locked.disabled") + 
                    "，" + I18n.getMessage("auth.user.contact.admin");
        } else {
            UserLock lockStatus = UserLock.valueOf(status);
            message = I18n.getMessage(lockStatus.getDescription()) +
                    "，" + I18n.getMessage(MessageConstants.AUTH_USER_UNLOCK_TIME_LIMIT);
        }
        return Mono.just(ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .body(Result.fail(new UserVo())
                        .code(HttpStatus.FORBIDDEN.value())
                        .message(message)));
    }

    /**
     * 创建登录失败响应
     */
    private Mono<ResponseEntity<Result<UserVo>>> createLoginFailResponse() {
        return Mono.just(ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .body(Result.fail(new UserVo())
                        .code(HttpStatus.FORBIDDEN.value())
                        .message(I18n.getMessage(MessageConstants.AUTH_USERNAME_PASSWORD_ERROR))));
    }

    /**
     * 获取 Authorization 并提取用户名
     */
    private Mono<String> extractUsername(ServerHttpRequest request) {
        try {
            String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
            if (authHeader != null && authHeader.startsWith("Bearer ")) {
                String authToken = authHeader.substring(7);
                return Mono.justOrEmpty(jwt.extractUsername(authToken));
            }
        } catch (Exception e) {
            return Mono.empty();
        }
        return Mono.empty();
    }

    /**
     * 通用处理授权和执行操作的方法
     */
    private Mono<ResponseEntity<Result<Object>>> handleAuthorizedRequest(ServerWebExchange request, Function<String, Mono<Result<Object>>> action) {

        return extractUsername(request.getRequest())
                .flatMap(username -> action.apply(username)
                        
                        .map(ResponseEntity::ok))
                        .defaultIfEmpty(ResponseEntity
                                .status(HttpStatus.UNAUTHORIZED)
                                .body(Result.fail()
                                        .code(HttpStatus.UNAUTHORIZED.value())
                                        .message(I18n.getMessage(MessageConstants.AUTH_NOT_LOGIN))));
    }

    /**
     * 获取用户信息
     */
    @GetMapping("user/info")
    public Mono<ResponseEntity<Result<Object>>> info(ServerWebExchange request) {
        return handleAuthorizedRequest(request, username ->
                authService.getUserRoles(username)
                        .map(user -> {
                            user.setPassword("******"); // 隐藏用户密码
                            return Result.ok(user);
                        })
        );
    }

    /**
     * 获取用户页面操作的权限码
     */
    @GetMapping("codes")
    public Mono<ResponseEntity<Result<Object>>> codes(ServerWebExchange request) {
        return handleAuthorizedRequest(request, username ->
                authService.getUserPermissionCodes(username)
                        .collectList()
                        .map(codesList -> Result.ok(codesList.toArray(new String[0]))) // 返回权限码数组
        );
    }

    /**
     * 获取用户菜单列表
     */
    @GetMapping("menu/all")
    public Mono<ResponseEntity<Result<Object>>> getUserMenus(ServerWebExchange request) {
        return handleAuthorizedRequest(request, username ->
                authService.getUserMenus(username).map(Result::ok)
        );
    }

    /**
     * 用户登出
     */
    @PostMapping("/logout")
    public Mono<ResponseEntity<Result<Object>>> logout(ServerWebExchange request) {
        return Mono.just(ResponseEntity.ok(Result.ok()
                .message(I18n.getMessage(MessageConstants.AUTH_LOGOUT_SUCCESS))));
    }

    /**
     * 修改密码
     */
    @LogCollector
    @PostMapping("/change-password")
    public Mono<ResponseEntity<Result<Object>>> changePassword(@RequestBody UserVo changePasswordRequest) {
        // 直接通过账号密码验证身份，无需令牌验证
        return authService.findByUsername(changePasswordRequest.getUsername())
                .flatMap(user -> validateAndChangePassword(user, changePasswordRequest))
                .map(result -> ResponseEntity.ok(result))
                .switchIfEmpty(Mono.defer(() -> 
                    Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                            .body(Result.fail().message(I18n.getMessage(MessageConstants.AUTH_USERNAME_PASSWORD_ERROR))))));
    }

    /**
     * 验证并修改密码
     */
    private Mono<Result<Object>> validateAndChangePassword(UserPo user, UserVo changePasswordRequest) {
        // 验证当前密码
        if (!passwordEncoder.matches(changePasswordRequest.getPassword(), user.getPassword())) {
            return Mono.just(Result.fail().message(I18n.getMessage(MessageConstants.USER_PASSWORD_OLD_INCORRECT)));
        }

        // 验证新密码格式
        String newPassword = changePasswordRequest.getRealName(); // 使用realName字段传递新密码
        if (!isValidPasswordFormat(newPassword)) {
            return Mono.just(Result.fail().message(I18n.getMessage("first.login.change.password.password.format")));
        }

        // 验证新密码不能与旧密码相同
        if (passwordEncoder.matches(newPassword, user.getPassword())) {
            return Mono.just(Result.fail().message(I18n.getMessage("first.login.change.password.password.same")));
        }

        // 验证新密码二次输入
        String confirmPassword = changePasswordRequest.getEmail(); // 使用email字段传递确认密码
        if (!newPassword.equals(confirmPassword)) {
            return Mono.just(Result.fail().message(I18n.getMessage("first.login.change.password.password.mismatch")));
        }

        // 更新密码
        user.setPassword(passwordEncoder.encode(newPassword));
        
        // 密码修改成功后，更新最后登录时间
        user.setLastLoginTime(System.currentTimeMillis());
        user.setUpdateTime(System.currentTimeMillis());
        
        return authService.updatePasswordAndLastLoginTime(user)
                .then(Mono.just(Result.ok().message(I18n.getMessage("first.login.change.password.success"))))
                .onErrorResume(e -> Mono.just(Result.fail().message(I18n.getMessage("first.login.change.password.failed"))));
    }

    /**
     * 验证密码格式
     */
    private boolean isValidPasswordFormat(String password) {
        if (password == null || password.length() < 8) {
            return false;
        }
        
        boolean hasUpperCase = false;
        boolean hasLowerCase = false;
        boolean hasDigit = false;
        boolean hasSpecialChar = false;
        
        for (char c : password.toCharArray()) {
            if (Character.isUpperCase(c)) {
                hasUpperCase = true;
            } else if (Character.isLowerCase(c)) {
                hasLowerCase = true;
            } else if (Character.isDigit(c)) {
                hasDigit = true;
            } else {
                hasSpecialChar = true;
            }
        }
        
        return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar;
    }

}
